﻿/**
 * 
 * Copyright 2009 Mike Jamer [email: mikejamer@gmail.com ]
 * 
 * This File is part of Moose IRC
 * 
 * MooseIRC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * MooseIRC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MooseIRC.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

using mooseirc.irc;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Reflection;

namespace mooseirc.utility
{
    /// <summary>
    /// used to find things..
    /// reflectively
    /// </summary>
    /// <remarks>
    /// Provides ability to dynamically load external assemblies, and to search for contents within,
    /// this is fine for times we want to specify by name, but the reality is, this may never occur.
    /// and it is more likely that while we will import the name of the command, we will be looking for commands based on
    /// triggers.
    /// 
    /// So will be updating these to search and cache by triggers, or possibly to buils a matrix, that maps
    /// triggers to drivers and names.
    /// 
    /// </remarks>
    internal static class DriverUtility
    {

        /// <summary>
        /// Cached Drivers
        /// </summary>
        private static Dictionary<string, Assembly> m_dictDrivers;

        /// <summary>
        /// Cross References Commands by triggers
        /// </summary>
        private static Dictionary<string, IrcCommand> m_dictCommandTriggers;

        /// <summary>
        /// Cross References Responses by triggers
        /// </summary>
        private static Dictionary<string, IrcResponse> m_dictResponseTriggers;

        /// <summary>
        /// Cached Commands
        /// </summary>
        private static Dictionary<Assembly, Dictionary<string, IrcCommand>> m_dictCommands;

        /// <summary>
        /// Cached Responses
        /// </summary>
        private static Dictionary<Assembly, Dictionary<string, IrcResponse>> m_dictResponses;

        /// <summary>
        /// Instantiate Static Members
        /// </summary>
        static DriverUtility()
        {
            m_dictDrivers = new Dictionary<string, Assembly>();
            m_dictCommandTriggers = new Dictionary<string, IrcCommand>();
            m_dictResponseTriggers = new Dictionary<string, IrcResponse>();
            m_dictCommands = new Dictionary<Assembly, Dictionary<string, IrcCommand>>();
            m_dictResponses = new Dictionary<Assembly, Dictionary<string, IrcResponse>>();

            //preloadLocal();
            preloadAllDrivers(); //Load up all drivers visible
        }

        /// <summary>
        /// Get Driver Assembly
        /// </summary>
        /// <param name="driverName">name of driver</param>
        /// <returns>assembly, marked as desired driver</returns>
        public static Assembly getDriver(string driverName)
        {
            Assembly asm = null;
            object[] attributes;

            if (m_dictDrivers.ContainsKey(driverName)) //If we have driver cached, use it
            {
                return m_dictDrivers[driverName];
            }
            else //otherwise, locate it and cache it
            {
                foreach (string assemblyPath in System.IO.Directory.GetFiles(Settings.SettingsStructure["general"]["drivers_directory"], "*.dll"))
                {
                    try
                    {
                        asm = Assembly.LoadFile(assemblyPath);
                        attributes = asm.GetCustomAttributes(typeof(DriverAttribute), false);
                        if (attributes.Length == 1)
                        {
                            if (((DriverAttribute)attributes[0]).name == driverName)
                            {
                                m_dictDrivers[driverName] = asm;
                                return m_dictDrivers[driverName];
                            }
                        }
                    }
                    catch (Exception)
                    {
                        Console.Error.WriteLine(assemblyPath + " failed to load");
                    }

                }
                return null; //If we can't find it, return null
            }
        }

        /// <summary>
        /// Loads local commands and attributes
        /// </summary>
        public static void preloadLocal()
        {
            Assembly asm = Assembly.GetCallingAssembly();
            m_dictDrivers["local"] = asm;

            preloadAllCommands(asm);
            preloadAllResponses(asm);

            return;

        }

        /// <summary>
        /// Pre-Load all Drivers, Comamnds and Responses
        /// </summary>
        public static void preloadAllDrivers()
        {
            Assembly asm = null;
            object[] attributes;
            string driverName = string.Empty;
            foreach (string assemblyPath in System.IO.Directory.GetFiles(Settings.SettingsStructure["general"]["drivers_directory"], "*.dll"))
            {
                try
                {
                    asm = Assembly.LoadFile(assemblyPath);
                    attributes = asm.GetCustomAttributes(typeof(DriverAttribute), false);
                    if (attributes.Length == 1)
                    {
                        driverName = ((DriverAttribute)attributes[0]).name;
                        if (((DriverAttribute)attributes[0]).name == driverName)
                        {
                            m_dictDrivers[driverName] = asm;
                            m_dictCommands[asm] = new Dictionary<string, IrcCommand>();
                            m_dictResponses[asm] = new Dictionary<string, IrcResponse>();
                            preloadAllCommands(asm);
                            preloadAllResponses(asm);
                        }
                    }
                }
                catch (Exception)
                {
                    Console.Error.WriteLine(assemblyPath + " failed to load");
                }
            }
            return;
        }

        /// <summary>
        /// Preloads all commands for a driver
        /// </summary>
        /// <param name="driver">Driver to load from</param>
        public static void preloadAllCommands(Assembly driver)
        {
            object[] attributes;
            string commandName = string.Empty;
            foreach (Type typ in driver.GetTypes())
            {
                attributes = typ.GetCustomAttributes(typeof(CommandAttribute), false);
                if (attributes.Length > 0)
                {
                    commandName = ((CommandAttribute)attributes[0]).Name;
                    m_dictCommands[driver][commandName] = (IrcCommand)driver.CreateInstance(typ.ToString(), false, BindingFlags.Default, null, null, null, null);

                    //Apply Trigger Map
                    attributes = typ.GetCustomAttributes(typeof(TriggerAttribute), false);
                    foreach (object trigger in attributes)
                    {
                        string triggerName = ((TriggerAttribute)trigger).Trigger;
                        m_dictCommandTriggers[triggerName] = m_dictCommands[driver][commandName];
                    }
                }
            }
        }

        /// <summary>
        /// Preloads all requests for a driver
        /// </summary>
        /// <param name="driver">Driver to load from</param>
        public static void preloadAllResponses(Assembly driver)
        {
            object[] attributes;
            string responseName = string.Empty;
            foreach (Type typ in driver.GetTypes())
            {
                attributes = typ.GetCustomAttributes(typeof(ResponseAttribute), false);
                if (attributes.Length > 0)
                {
                    responseName = ((ResponseAttribute)attributes[0]).Name;
                    m_dictResponses[driver][responseName] = (IrcResponse)driver.CreateInstance(typ.ToString(), false, BindingFlags.Default, null, null, null, null);

                    //Apply Trigger Map
                    attributes = typ.GetCustomAttributes(typeof(TriggerAttribute), false);
                    foreach (object trigger in attributes)
                    {
                        string triggerName = ((TriggerAttribute)trigger).Trigger;
                        m_dictResponseTriggers[triggerName] = m_dictResponses[driver][responseName];
                    }
                }
            }
        }

        /// <summary>
        /// Gets Irc Command by Trigger Name
        /// </summary>
        /// <param name="trigger_name">name of trigger</param>
        /// <returns>reference of command instance</returns>
        public static IrcCommand getCommand(string trigger_name)
        {
            try {
                return m_dictCommandTriggers[trigger_name];
            } catch (Exception) {
                return null;
            }
        }

        /// <summary>
        /// Return, or create and return specified command
        /// </summary>
        /// <param name="driver">driver containing command</param>
        /// <param name="responseName">name of command</param>
        /// <returns>instance of command</returns>
        public static IrcCommand getCommand(Assembly driver, string commandName)
        {
            object[] attributes;
            if (m_dictCommands.ContainsKey(driver) && m_dictCommands[driver].ContainsKey(commandName))
            {
                return m_dictCommands[driver][commandName]; 
            }
            else 
            {
                if (!m_dictCommands.ContainsKey(driver)) 
                {
                    m_dictCommands[driver] = new Dictionary<string, IrcCommand>();
                }

                foreach (Type typ in driver.GetTypes())
                {
                    if (typ.GetInterfaces().Contains(typeof(IrcCommand)) 
                        && (attributes = typ.GetCustomAttributes(typeof(CommandAttribute), false)).Length > 0 
                        && ((CommandAttribute)attributes[0]).Name == commandName) 
                    {
                        m_dictCommands[driver][commandName] = (IrcCommand)driver.CreateInstance(typ.ToString(), false, BindingFlags.Default, null, null, null, null);

                        //Apply Trigger Map
                        attributes = typ.GetCustomAttributes(typeof(TriggerAttribute), false);
                        foreach (object trigger in attributes)
                        {
                            string triggerName = ((TriggerAttribute)trigger).Trigger;
                            m_dictCommandTriggers[triggerName] = m_dictCommands[driver][commandName];
                        }

                        return m_dictCommands[driver][commandName]; 
                    }
                }
            }

            return null; //If we can't find it, return null
        }

        /// <summary>
        /// Gets Irc Response by Trigger Name
        /// </summary>
        /// <param name="trigger_name">name of trigger</param>
        /// <returns>reference of response instance</returns>
        public static IrcResponse getResponse(string trigger_name)
        {
            try {
                return m_dictResponseTriggers[trigger_name];
            } catch (Exception) {
                return null;
            }
        }

        /// <summary>
        /// Return or create and return Response specified
        /// </summary>
        /// <param name="driver">driver containing response specified</param>
        /// <param name="responseName">name of resposne</param>
        /// <returns>instance of response</returns>
        public static IrcResponse getResponse(Assembly driver, string responseName)
        {
            object[] attributes;
            if (m_dictResponses.ContainsKey(driver) && m_dictResponses[driver].ContainsKey(responseName))
            {
                return m_dictResponses[driver][responseName];
            }
            else 
            {
                if (!m_dictResponses.ContainsKey(driver)) 
                {
                    m_dictResponses[driver] = new Dictionary<string, IrcResponse>(); 
                }
                foreach (Type typ in driver.GetTypes())
                {
                    if (typ.GetInterfaces().Contains(typeof(IrcResponse))
                        && (attributes = typ.GetCustomAttributes(typeof(ResponseAttribute), false)).Length > 0 
                        && ((ResponseAttribute)attributes[0]).Name == responseName) 
                    {
                        m_dictResponses[driver][responseName] = (IrcResponse)driver.CreateInstance(typ.ToString(), false, BindingFlags.Default, null, null, null, null);

                        //Apply Trigger Map
                        attributes = typ.GetCustomAttributes(typeof(TriggerAttribute), false);
                        foreach (object trigger in attributes)
                        {
                            string triggerName = ((TriggerAttribute)trigger).Trigger;
                            m_dictResponseTriggers[triggerName] = m_dictResponses[driver][responseName];
                        }

                        return m_dictResponses[driver][responseName];
                    }
                }
            }

            return null; //If we can't find it, return null
        }

    }
}
